
#include "PageCache.h"
#include "CentralCache.h"

CentralCache CentralCache::_sInst;
//大胆猜测:这里进行是对不同哈希桶的span切割,将span里的_freelist的void*类型指定为我们需要的类型
//        例如申请32KB,就会返回32KB的类型给_freelist,充当之后程序的start指针
//        因此里面还会有一个向PageCache申请span的函数调用,会返回指定页大小的页
//        例如32KB,则会去PageCache里的4page桶要一个32KB的span回来

//获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    //i++;
    //if (i == 32)
    //{
    //    int x = 0;
    //}
    // 查看当前的spanlist中是否还有未分配对象的span
    Span* it = list.Begin();
    while (it != list.End())
    {
        if (it->_freeList != nullptr)// span的freelist切分对象不为空
            return it;

        else
            it = it->_next;
    }

    //先把central cache的桶锁解除掉,这样其他线程就可以释放内存,不会被阻塞
    list._mtx.unlock();
    //走到这里说明没有空闲的span,只能找Page Cache要
    //要多少个?
    PageCache::GetInstance()->_pageMtx.lock();
    Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
    span->_isUse = true;
    span->_objSize = size;
    PageCache::GetInstance()->_pageMtx.unlock();

    //对获取的span进行切分不需要加锁,因为其他线程拿不到这个span

    //计算span的大块内存的起始地址和大块内存的大小(字节数)
    char* start = (char*)(span->_pageId << PAGE_SHIFT); // 计算span的地址,用编号×8KB
    size_t bytes = span->_n << PAGE_SHIFT;                     // 判断该span的大小(字节),用n个页数×8KB,一个页数有8KB
    char* end = start + bytes;                                             //end即是这个span描述的内存空间末尾
    //将大块内存切成自由链表连接起来
    //这里用尾插
    span->_freeList = start;
    start += size;
    void* tail = span->_freeList;
    while (start < end)
    {
        Nextobj(tail) = start;
        tail = Nextobj(tail);
       /* tail = start;*/
        start += size;
    }
    NextObj(tail) = nullptr;
    //切好后,要将span挂到spanlist上时再申请锁
    list._mtx.lock();
    list.PushFront(span);
    return span;
}

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
    size_t index = SizeClass::Index(size);
    _spanLists[index]._mtx.lock();

    Span* span = GetOneSpan(_spanLists[index], size);
    assert(span);
    assert(span->_freeList);

    //从span中获取batchNum个对象
    //有多少个小内存块就拿多少个，哪怕拿不到bacthNum个也要返回获取到的小内存块数量

    start = span->_freeList;
    end = start;
    //让end走batchNum-1步,之后直接让_freelist指向end的下一个(Nextobj(end))
    size_t i = 0;
    //start起始位置就已经拿了一个小内存块了
    size_t actualNum = 1; // span最少都有一个小内存块,如果一个都没有那就应该抛异常了

    // 让end走batchNum-1步,停下来的位置就刚好有batchNum,此时再让span的第一个节点指向end的下一个节点即可
    while (i < batchNum - 1 && Nextobj(end) != nullptr)
    {
        end = Nextobj(end);
        i++;
        actualNum++;
    }
    span->_freeList = Nextobj(end);
    Nextobj(end) = nullptr;
    span->_useCount += actualNum;

    _spanLists[index]._mtx.unlock();

    return actualNum;
}

//给size是为了清楚给哪一个桶
void CentralCache::ReleaseListToSpans(void* start, void* end, size_t size)
{
    size_t index = SizeClass::Index(size);
    _spanLists[index]._mtx.lock();
    //如何区分不同内存块属于哪一个span？
    //例如有一个页号为2000的span,那他的所属内存块/8KB都是2000,因为不够除
    //因此所属内存块的地址用%8KB的方式去判断在span里的哪一个位置，用/8KB(>>3)判断在哪一个span

    while (start)
    {
        void* next = Nextobj(start);

        Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
        Nextobj(start) = span->_freeList;
        span->_freeList = start;
        span->_useCount--;
        // 说明span的切分出去的所以小块内存都回来了
        // 这个span就可以再回收给Page Cache,pagecache就可以尝试做前后页的合并
        if (span->_useCount == 0)
        {
            _spanLists[index].Erase(span);
            span->_freeList = nullptr;
            span->_next = nullptr;
            span->_prev = nullptr;

            // 释放span给page cache时,使用page cache的锁即可
            // 这时候就要解除桶锁了
            _spanLists[index]._mtx.unlock();

            PageCache::GetInstance()->_pageMtx.lock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
            PageCache::GetInstance()->_pageMtx.unlock();

            _spanLists[index]._mtx.lock();
        }

        start = next;
    }


    _spanLists[index]._mtx.unlock();
}
